//@version=5
indicator("Momentum Flow Build w/ FVG v2", overlay=true)


// Session 1 Inputs
session1_enabled = input.bool(true, title="Enable Session 1", group="🕖 Session 1")
session1_time = input.session("1800-0000", title="Session 1 Time", group="🕖 Session 1")
session1_border_color = input.color(#ffed4f, title="Session 1 Border Color", group="🕖 Session 1")
session1_bg_color = input.color(color.new(#ffed4f, 90), title="Session 1 Background Color", group="🕖 Session 1")

// Session 2 Inputs
session2_enabled = input.bool(true, title="Enable Session 2", group="🕖 Session 2")
session2_time = input.session("0000-0600", title="Session 2 Time", group="🕖 Session 2")
session2_border_color = input.color(color.blue, title="Session 2 Border Color", group="🕖 Session 2")
session2_bg_color = input.color(color.new(color.rgb(255, 229, 79), 90), title="Session 2 Background Color", group="🕖 Session 2")

// Session 3 Inputs
session3_enabled = input.bool(true, title="Enable Session 3", group="🕖 Session 3")
session3_time = input.session("0930-0935", title="Session 3 Time", group="🕖 Session 3")
session3_border_color = input.color(color.new(color.yellow,0), title="Session 3 Border Color", group="🕖 Session 3")
session3_bg_color = input.color(color.new(color.yellow, 90), title="Session 3 Background Color", group="🕖 Session 3")

// Helper Functions
is_new_bar(sess) =>
    t = time("D", sess, "America/New_York")
    na(t[1]) and not na(t) or t[1] < t

is_in_session(sess) =>
    not na(time("D", sess, "America/New_York"))

// Function to Calculate Session End Time
get_session_end(sess, durationHours, durationMinutes) =>
    t = time("D", sess, "America/New_York")
    if na(t)
        na
    else
        hour = hour(t, "America/New_York")
        minute = minute(t)
        if hour == 0 and minute == 0
            t + (durationHours * 60 * 60 * 1000) + (durationMinutes * 60 * 1000)  // Example for session ending at 6 AM
        else
            t + (durationHours * 60 * 60 * 1000) + (durationMinutes * 60 * 1000)

// Function to Handle Each Session
plot_session_boxes(session_enabled, session_time, border_color, bg_color, durationHours, durationMinutes) =>
    var box[] session_boxes = array.new_box()
    if session_enabled and is_in_session(session_time)
        // Calculate session boundaries and high/low
        new_bar = is_new_bar(session_time)
        var float session_low = na
        var float session_high = na
        session_low := new_bar ? low : (na(session_low) ? low : math.min(session_low, low))
        session_high := new_bar ? high : (na(session_high) ? high : math.max(session_high, high))

        var int session_start = na
        session_start := new_bar ? time : session_start

        // Calculate session_end dynamically, extending into the future if ongoing
        session_end = get_session_end(session_time, durationHours, durationMinutes)

        // Create or update box
        if new_bar
            session_box = box.new(left=session_start, bottom=session_low, right=session_end, top=session_high, border_width=1, xloc=xloc.bar_time, border_style=line.style_solid, border_color=border_color, bgcolor=bg_color)
            array.push(session_boxes, session_box)
        else
            if array.size(session_boxes) > 0
                box.set_right(array.get(session_boxes, array.size(session_boxes) - 1), session_end)
                box.set_top(array.get(session_boxes, array.size(session_boxes) - 1), session_high)
                box.set_bottom(array.get(session_boxes, array.size(session_boxes) - 1), session_low)


// Plot Session 1 Boxes
plot_session_boxes(session1_enabled, session1_time, session1_border_color, session1_bg_color, 6, 0)

// Plot Session 2 Boxes
plot_session_boxes(session2_enabled, session2_time, session2_border_color, session2_bg_color, 6, 0)

// Plot Session 3 Boxes
plot_session_boxes(session3_enabled, session3_time, session3_border_color, session3_bg_color, 1, 30)
// ==============================
// ✨ Fair Value Gaps (FVG)
// ==============================

// Inputs for FVG
grpFVG = "✨ Fair Value Gaps"
fvg_enabled      = input.bool(true,  "Enable FVG Detection", group=grpFVG)
fvg_extend_fill  = input.bool(true,  "Extend Boxes Until Filled", group=grpFVG, tooltip="If enabled, FVG boxes will extend to the right until price fully fills the gap.")
fvg_min_pct      = input.float(0.00, "Min Gap Size (% of Price)", step=0.01, group=grpFVG, tooltip="Filter out tiny gaps. Size is measured as a percent of current price.")
fvg_bull_color   = input.color(color.new(color.lime, 62), "Bullish FVG Color", group=grpFVG)
fvg_bear_color   = input.color(color.new(color.red, 62),  "Bearish FVG Color", group=grpFVG)

// Internal state for FVGs
var box[]   fvg_boxes   = array.new_box()
var float[] fvg_top     = array.new_float()  // UPPER level of gap (always upper)
var float[] fvg_bottom  = array.new_float()  // LOWER level of gap (always lower)
var bool[]  fvg_isBull  = array.new_bool()
var bool[]  fvg_active  = array.new_bool()

// Helper: add a new FVG box
f_add_fvg(isBull, top, bottom) =>
    // 'top' must be the upper boundary; 'bottom' the lower boundary
    bcolor = isBull ? fvg_bull_color : fvg_bear_color
    bx = box.new(left = time, right = time, top = top, bottom = bottom, xloc = xloc.bar_time, bgcolor = bcolor, border_color = bcolor)
    array.push(fvg_boxes,  bx)
    array.push(fvg_top,    top)
    array.push(fvg_bottom, bottom)
    array.push(fvg_isBull, isBull)
    array.push(fvg_active, true)

// Detect formation on the third candle (A, B, C pattern where we are at C)
// Bullish FVG: low (C) > high (A)
// Bearish FVG: high (C) < low (A)
if fvg_enabled and bar_index >= 2
    bull_cond = low > high[2]
    bear_cond = high < low[2]

    // Gap size based on the definition: distance between the non-overlapping wicks
    bull_size = bull_cond ? (low - high[2]) : 0.0
    bear_size = bear_cond ? (low[2] - high) : 0.0

    // Percent-of-price filter (use current close as basis)
    bull_ok = bull_cond and (fvg_min_pct <= 0 or (bull_size / close) * 100 >= fvg_min_pct)
    bear_ok = bear_cond and (fvg_min_pct <= 0 or (bear_size / close) * 100 >= fvg_min_pct)

    if bull_ok
        // Bullish gap region: upper=low, lower=high[2]
        f_add_fvg(true,  low,    high[2])
    if bear_ok
        // Bearish gap region: upper=low[2], lower=high   (FIXED to keep 'top' as the upper boundary)
        f_add_fvg(false, low[2], high)

// Maintain and optionally stop extending on fill
if fvg_enabled and array.size(fvg_boxes) > 0
    for i = 0 to array.size(fvg_boxes) - 1
        isActive = array.get(fvg_active, i)
        if isActive
            bx   = array.get(fvg_boxes, i)
            top  = array.get(fvg_top, i)       // always the upper boundary
            bot  = array.get(fvg_bottom, i)    // always the lower boundary
            bull = array.get(fvg_isBull, i)

            // Extend right edge each bar
            box.set_right(bx, time)

            // Correct fill checks with our stored convention:
            //  - Bullish FVG fills when price trades DOWN to the LOWER boundary (bot)
            //  - Bearish FVG fills when price trades UP to the UPPER boundary (top)
            filled = bull ? (low  <= bot) : (high >= top)

            if filled and fvg_extend_fill
                // Stop extending further (freeze right edge at current time)
                array.set(fvg_active, i, false)

// Alerts — on formation
// Alerts — only when the 3rd candle CLOSES confirming the FVG
alertcondition(
     fvg_enabled and bar_index >= 2 and barstate.isconfirmed and (low > high[2] or high < low[2]),
     title   = "Confirmed FVG Formed",
     message = "Confirmed Fair Value Gap formed on {{ticker}} at {{time}}"
     )
